/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 spell_sparks.cpp

	$Header: /heroes4/spell_sparks.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "spell_sparks.h"

#include "angle.h"
#include "battlefield.h"
#include "bound_handler.h"
#include "can_cast.h"
#include "combat_actor_model_cache.h"
#include "combat_creature.h"
#include "combat_footprint.h"
#include "combat_path_finder.h"
#include "combat_spell_registration.h"
#include "counted_animation.h"
#include "counted_idle_processor.h"
#include "external_string.h"
#include "game_time.h"
#include "magic_mirror.h"
#include "spell_actor_animation.h"

// -----------------------------------------------------------------
// animation for sparks
// -----------------------------------------------------------------
namespace
{
	typedef t_counted_ptr<t_spell_sparks> t_sparks_ptr;

	// -----------------------------------------------------------------
	// inflict sparks damage
	// -----------------------------------------------------------------
	class t_sparks_damage : public t_handler_base
	{
	public:
		t_sparks_damage( t_combat_creature_ptr caster, t_combat_creature_list const& targets, 
			             int power, t_combat_action_message const & message );

		virtual void operator()();
	protected:
		t_combat_creature_ptr   m_caster;
		t_combat_action_message m_message;
		t_combat_creature_list  m_targets;
		int					    m_power;
	};
};

// -----------------------------------------------------------------
// sparks combat spell
// -----------------------------------------------------------------
t_combat_creature_list get_sparks_targets( t_combat_creature* caster, t_direction direction )
{
	int const                 k_arc = 180;
	int						  attack_distance = 3;
	t_map_point_2d			  position		  = caster->get_cell_position();
	t_map_point_2d			  center		  = caster->get_footprint_center();
	int						  footprint_size  = caster->get_footprint_size() 
		                                        + attack_distance * 2;
	t_combat_footprint const& footprint		  = get_combat_footprint( footprint_size );
	int                       angle;
	t_map_point_2d            offset;
	t_map_point_2d			  point;
	t_combat_creature*        candidate;
	int		                  base_angle;
	t_battlefield&            battlefield = caster->get_battlefield();
	t_combat_creature_list    targets;

	base_angle = get_angle( direction );
	position.row	-= attack_distance;
	position.column -= attack_distance;
	for (offset.row = 0; offset.row < footprint_size; offset.row++)
	{
		for (offset.column = 0; offset.column < footprint_size; offset.column++)
		{
			// check if point is in attack area
			if (!footprint[offset.row][offset.column])
				continue;
			// check if point is a valid square
			point = position + offset;
			if (!battlefield.is_valid( point ))
				continue;
			// get creature in square
			candidate = battlefield.get_creature( point );
			if (candidate == 0)
				continue;
			// check if creature is friendly
			if (candidate == caster)
				continue;
			if (!can_affect( *caster, k_spell_burning_hands, *candidate ))
				continue;

			// check if creature has already been attacked
			t_combat_creature_iterator index = targets.begin();
			t_combat_creature_iterator end   = targets.end();

			for (; index != end; index++)
			{
				if (index->get() == candidate)
					break;
			}
			if (index != end)
				continue;

			// check if square is in attack arc
			point = (point << k_battlefield_subcell_shift) + k_battlefield_half_cell;
			angle = get_angle( center, point );
			angle = get_difference( angle, base_angle );
			if (angle * 2 > k_arc)
				continue;
			targets.push_back( candidate );
		}
	}
	return targets;
}

// -----------------------------------------------------------------
// inflict sparks damage
// -----------------------------------------------------------------
t_sparks_damage::t_sparks_damage( t_combat_creature_ptr caster, 
								  t_combat_creature_list const& targets, 
								  int power,
								  t_combat_action_message const & message)
{
	m_caster = caster;
	m_targets = targets;
	m_power = power;
	m_message = message;
}

// -----------------------------------------------------------------
// inflict sparks damage
// -----------------------------------------------------------------
void t_sparks_damage::operator()()
{
	t_combat_creature_iterator index = m_targets.begin();
	t_combat_creature_iterator end   = m_targets.end();

	for (; index != end; index++)
	{
		int damage;

		damage = index->get()->modify_spell_damage( m_caster, m_power, k_spell_burning_hands );
		index->get()->add_damage( damage, m_caster, false, false, m_message );
		index->get()->check_flinch( m_message );
		index->get()->apply_damage( true );
	}
}

// ------------------------------------------------------------------------------
// get appropriate sparks model
// ------------------------------------------------------------------------------
static t_combat_actor_model_cache_set k_sparks_model( "spells.sparks" );

// -----------------------------------------------------------------
// sparks combat spell
// -----------------------------------------------------------------
static t_combat_spell_registration<t_spell_sparks>
       k_sparks_register( k_spell_burning_hands );

// -----------------------------------------------------------------
// sparks combat spell
// -----------------------------------------------------------------
t_spell_sparks::t_spell_sparks( t_battlefield& battlefield, t_spell spell )
              : t_combat_spell( battlefield, spell )
{
}

// -----------------------------------------------------------------
// sparks combat spell
// -----------------------------------------------------------------
bool t_spell_sparks::begin_casting()
{
	if (m_battlefield.is_computer_controlled( *m_caster ))
		move_to_attack( m_attack_point );
	return true;
}


double t_spell_sparks::get_cancel_weight( t_combat_creature const& target ) const
{
	return 0.0;
}

// -----------------------------------------------------------------
// inflict sparks damage
// -----------------------------------------------------------------
bool t_spell_sparks::cast_on( t_combat_creature_ptr caster, t_combat_creature_ptr target, int power, bool is_mirror )
{
	return true;
}

// -----------------------------------------------------------------
// sparks combat spell
// -----------------------------------------------------------------
void t_spell_sparks::cast( t_combat_creature_ptr caster, t_direction base_direction, int power,
						   t_combat_creature_list const& targets, bool is_mirror )
{
	// create sparks animations
	int						offset;
	t_direction	   			direction;
	t_combat_actor*			sparks;
	double					scale = m_battlefield.get_model_scale();
	t_map_point_2d			position;
	t_handler				end_handler;
	t_combat_action_message message( *caster, get_action_text( is_mirror ) );

	play_sound();
	end_handler = new t_sparks_damage( caster, targets, power, message );
	for (offset = -1; offset <= 1; offset++)
	{
		direction = t_direction( (base_direction + offset + 8) & 7 );
		sparks = new t_unsaved_combat_actor( &m_battlefield, k_sparks_model.get( scale ),
			                         k_combat_actor_action_melee, direction );
		sparks->move( m_battlefield, t_map_point_3d(0,0,0) );
		position = caster->get_footprint_center();
		position -= sparks->get_footprint_center();
		position += get_direction_offset( direction )
			        * (caster->get_footprint_size() << (k_battlefield_subcell_shift - 1));
		m_battlefield.place_object( sparks, position );

		t_spell_actor_animation_ptr animation = new t_spell_actor_animation( m_battlefield, 
																			 *sparks, 
																			 this, 
																			 end_handler );

		animation->display_action_message( message );
		sparks->set_animation( animation.get() );
		end_handler = 0;
	}
}

// -----------------------------------------------------------------
// sparks combat spell
// -----------------------------------------------------------------
void t_spell_sparks::execute( t_combat_creature& caster )
{
	t_combat_spell_ptr		ref = this;
	t_direction				direction = caster.get_current_direction();
	int						power;
	t_combat_creature_list  targets = get_sparks_targets( m_caster, direction );

	pay_cost();
	m_battlefield.set_spell( 0 );
	power = caster.get_spell_power( m_spell );
	cast( m_caster, direction, power, targets, false );

	t_combat_creature_iterator index = targets.begin();
	t_combat_creature_iterator end   = targets.end();

	for (; index != end; index++)
	{
		if (index->get()->has_magic_mirror())
			m_battlefield.add_action( new t_mirror_spell_action( *index, this, power ));
	}
}

// -----------------------------------------------------------------
// sparks combat spell
// -----------------------------------------------------------------
int t_spell_sparks::get_value( t_combat_creature* target ) const
{
	return 100;
}

static t_external_string const k_text_no_angle( "no_angle.sparks" );
static t_external_string const k_text_too_far( "too_far.sparks" );
extern t_external_string const k_text_no_enemies;
// -----------------------------------------------------------------
// sparks combat spell
// -----------------------------------------------------------------
t_mouse_window* t_spell_sparks::mouse_move( t_screen_point const& point,
									        std::string& help_text )
{
	t_combat_creature* creature = m_battlefield.creature_hit_test( point );
	t_mouse_window*    result;
	t_attack_angle     attack_angle;

	m_battlefield.select_target( creature );
	result = m_battlefield.get_blocked_cursor();
	if (creature == 0)
	{
		help_text = replace_spell_keywords( k_text_no_enemies, *m_caster, m_spell, 0 );
		return result;
	}

	result = m_battlefield.get_spell_cannot_cast_cursor();
	if (!::can_cast( *m_caster, m_spell, *creature, &help_text ))
		return result;

	if (!m_battlefield.get_attack_angle( point, creature, attack_angle ))
	{
		help_text = replace_spell_keywords( k_text_no_angle, *m_caster, m_spell, creature );
		return result;
	}

	if (attack_angle.move_distance > m_caster->get_combat_movement())
	{
		help_text = replace_spell_keywords( k_text_too_far, *m_caster, m_spell, creature );
		return result;
	}

	std::string temp;

	return m_battlefield.get_melee_cursor( point, creature, temp );
}

// -----------------------------------------------------------------
// sparks combat spell
// -----------------------------------------------------------------
void t_spell_sparks::begin_attack( t_combat_creature& caster )
{
	m_battlefield.begin_spell( m_spell_target );
}

// -----------------------------------------------------------------
// sparks combat spell
// -----------------------------------------------------------------
void  t_spell_sparks::move_to_attack( t_map_point_2d const& destination )
{
	t_combat_path_data const*		  path_point;
	t_combat_creature_handler		  handler;

	m_battlefield.begin_action();
	path_point = &m_battlefield.get_path_point( destination );
	if (path_point->move_cost > 0)
	{
		t_direction    direction;

		if (path_point->move_cost > m_caster->get_combat_movement())
		{
			m_battlefield.begin_move( destination );
			m_battlefield.set_spell( 0 );
			return;
		}
		handler = bound_handler( *this, &t_spell_sparks::begin_attack );
		m_battlefield.begin_move( destination, handler );
		direction = get_direction( *m_caster, destination, *m_spell_target );
		m_battlefield.preload( *m_caster, k_combat_actor_action_cast_spell, direction );
		return;
	}
	begin_attack( *m_caster );
	return;
}

// -----------------------------------------------------------------
// sparks combat spell
// -----------------------------------------------------------------
bool  t_spell_sparks::left_click( t_screen_point const& point )
{
	t_combat_creature* creature = m_battlefield.creature_hit_test( point );
	std::string        help_text;

	if (creature == 0)
		return false;

	if (!::can_cast( *m_caster, m_spell, *creature ))
		return false;

	t_attack_angle attack_angle;

	if (!m_battlefield.get_attack_angle( point, creature, attack_angle ))
		return false;
	if (attack_angle.move_distance > m_caster->get_combat_movement())
		return false;
	m_spell_target = creature;
	move_to_attack( attack_angle.point );
	return true;
}

// -----------------------------------------------------------------
// sparks combat spell
// -----------------------------------------------------------------
void t_spell_sparks::execute_mirror_spell( t_combat_creature_ptr creature, int power )
{
	t_direction            direction = get_direction( *creature, *m_caster );
	t_combat_creature_list targets = get_sparks_targets( creature, direction );

	cast( creature, direction, power, targets, true );
}

// --------------------------------------------------------------------
// Generate the list of casting actions the given caster could take
// for this spell
// --------------------------------------------------------------------
t_combat_ai_action_list t_spell_sparks::generate_combat_ai_action_list(t_combat_ai &owner)
{
	// Not implemented
	return t_combat_ai_action_list();
}

